﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ExpressionMapper
{
    using MappingExpressionGenerator = Func<Expression, Type, Expression, ParameterExpression, Expression>;

    /// <summary>
    /// Returns generator for Dictionary to Dictionary mappings.
    /// </summary>
    public static partial class Generators
    {
        public static MappingExpressionGenerator Serializable(MappingExpressionGenerator parent)
        {
            return
                (fromExpr, to, customMapping, dictionary) => IsSerializable(fromExpr.Type) && IsSerializable(to)?
                customMapping ?? GenerateSerializableExpression(fromExpr, to) : null;
        }

        private static bool IsSerializable(Type type)
        {
            return type.IsSerializable;
        }

        private static Expression GenerateSerializableExpression(Expression fromExpr, Type to)
        {
            Debug.WriteLine("\tSerializable ({0}): {1} to {2}", fromExpr.NodeType, fromExpr.Type.Name, to.Name);

            return Expression.Call(typeof(Generators)
                .GetMethod("SerializeInstance", BindingFlags.Static | BindingFlags.Public)
                .MakeGenericMethod(fromExpr.Type, to), fromExpr);
        }

        public static TOutput SerializeInstance<TInput,TOutput>(TInput instance)
        {
            if (IsSerializable(typeof(TInput)))
                 return (TOutput)(object)CloneViaBinarySerializer(instance) ;

             if (typeof(TInput).GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0)
                 return (TOutput)(object)CloneViaDataContractSerializer(instance);

            throw new NotImplementedException("!!!");
        }

        private static T CloneViaBinarySerializer<T>(T source)
        {
            var binaryFormatter = new BinaryFormatter();
            using (var memStream = new MemoryStream())
            {
                binaryFormatter.Serialize(memStream, source);

                memStream.Seek(0, SeekOrigin.Begin);

                return (T)binaryFormatter.Deserialize(memStream);
            }
        }

        private static T CloneViaDataContractSerializer<T>(T source)
        {
            var serializer = new DataContractSerializer(typeof(T));

            using (var stream = new MemoryStream())
            {
                serializer.WriteObject(stream, source);
                stream.Seek(0, SeekOrigin.Begin);
                return (T)serializer.ReadObject(stream);
            }
        }
    }
}